package com.yst.webplugin.component;


import com.yst.spring.context.SpringContext;
import com.yst.web.utils.ExceptionUtils;
import lombok.Data;
import lombok.Getter;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class ResultUtil {

    @Getter
    @Data
    public static class BeanMethod{
        private ControllerAdviceBean bean;
        private Method method;
        private Class<? extends Throwable> clazz;
        BeanMethod(ControllerAdviceBean bean,Method method, Class<? extends Throwable> clazz){
            this.bean=bean;
            this.method=method;
            this.clazz=clazz;
        }
    }
    private static final List<BeanMethod> beanMethodList=new ArrayList<>();
    private static final List<ControllerAdviceBean> adviceBeans=new ArrayList<>();
    private static void initExceptionHandler(){
        for (ControllerAdviceBean bean:adviceBeans){
            Method[] methods=bean.getBeanType().getDeclaredMethods();
            for(Method method:methods){
                ExceptionHandler exceptionHandler=method.getAnnotation(ExceptionHandler.class);
                if (Objects.nonNull(exceptionHandler)){
                    List<Class<? extends Throwable>> list= Arrays.asList(exceptionHandler.value());
                    for(Class<? extends Throwable> clazz:list){
                        BeanMethod beanMethod=new BeanMethod(bean,method,clazz);
                        beanMethodList.add(beanMethod);
                    }
                }
            }
        }
        beanMethodList.sort(Comparator.comparing(o -> Short.MAX_VALUE- ExceptionUtils.getDepth(o.clazz)));
    }
    private static final Map<Class<? extends Throwable>,BeanMethod> throwableCache=new ConcurrentHashMap<>();

    private static final Map<Class<?>,ControllerAdviceBean> writerCache=new ConcurrentHashMap<>();

    /**
     * fetch a bean from advice for handle throwable
     * @param throwable throwable
     * @return @see {@link BeanMethod}
     */
    public static BeanMethod getBeanMethod(Throwable throwable){
        return throwableCache.computeIfAbsent(throwable.getClass(),(key)->{
            for(BeanMethod beanMethod:beanMethodList){
                if (beanMethod.clazz.equals(throwable.getClass())){
                    return beanMethod;
                }
                if (beanMethod.clazz.isAssignableFrom(throwable.getClass())){
                    return beanMethod;
                }
            }
            return null;
        });
    }
    public static ControllerAdviceBean getBeanForResult(Object result){
        return writerCache.computeIfAbsent(result.getClass(),(key)->{
            for (ControllerAdviceBean adviceBean:adviceBeans){
                if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType()))
                {
                    if (((ResponseBodyAdvice)adviceBean.resolveBean()).supports(null,null))
                    {
                        return adviceBean;
                    }
                }
            }
            return null;
        });
    }
    private static final SpringContext.IContextReady onContextReady=(context)->{
        List<ControllerAdviceBean> beans= ControllerAdviceBean.findAnnotatedBeans(context);
        adviceBeans.addAll(beans);
        initExceptionHandler();
    };


    static {
        SpringContext.addOnReadyListener(onContextReady);
    }
}
